package com.ikingtech.platform.service.authorization;

import com.iking.framework.sdk.authorization.api.AuthorizationDeptApi;
import com.iking.framework.sdk.authorization.api.AuthorizationRoleApi;
import com.iking.framework.sdk.authorization.api.AuthorizationUserApi;
import com.iking.framework.sdk.authorization.model.AuthorizationRoleMenu;
import com.iking.framework.sdk.authorization.model.AuthorizationUser;
import com.iking.framework.sdk.authorization.model.AuthorizationUserDepartment;
import com.iking.framework.sdk.authorization.model.AuthorizationUserRole;
import com.ikingtech.framework.sdk.cache.constants.CacheConstants;
import com.ikingtech.framework.sdk.context.exception.FrameworkException;
import com.ikingtech.framework.sdk.context.security.Me;
import com.ikingtech.framework.sdk.enums.domain.DomainEnum;
import com.ikingtech.framework.sdk.enums.system.role.DataScopeTypeEnum;
import com.ikingtech.framework.sdk.enums.system.user.UserCategoryEnum;
import com.ikingtech.framework.sdk.utils.Tools;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.*;

import static com.ikingtech.framework.sdk.context.constant.SecurityConstants.GLOBAL_MENU_ID;

/**
 * @author tie yan
 */
@Service
@RequiredArgsConstructor
public class AuthorizationService {

    private final StringRedisTemplate redisTemplate;

    private final AuthorizationUserApi userApi;

    private final AuthorizationRoleApi roleApi;

    private final AuthorizationDeptApi deptApi;

    /**
     * 用户授权
     *
     * @param userId 用户ID
     * @return 用户ID
     */
    public String grant(String userId) {
        // 加载用户信息
        AuthorizationUser user = this.userApi.loadUser(userId);
        // 如果用户不存在，则抛出异常
        if (null == user) {
            throw new FrameworkException("userNotFound");
        }
        // 授权用户
        this.grant(user.getUsername(), user.getRoles(), user.getDepartments());
        // 返回用户ID
        return userId;
    }


    /**
     * 授权
     *
     * @param username    用户名
     * @param roles       角色列表
     * @param departments 部门列表
     */
    private void grant(String username, List<AuthorizationUserRole> roles, List<AuthorizationUserDepartment> departments) {
        // 根据角色id查询每个角色关联的菜单id
        List<AuthorizationRoleMenu> roleMenus = this.roleApi.loadMenu(Tools.Coll.convertList(roles, AuthorizationUserRole::getRoleId));
        if (Tools.Coll.isBlank(roleMenus)) {
            return;
        }
        Map<String, List<String>> menuIdMap = Tools.Coll.convertGroup(roleMenus, AuthorizationRoleMenu::getRoleId, AuthorizationRoleMenu::getMenuId);

        // key是menuId,value是部门全路径集合的map
        Map<String, List<String>> dataScopeMap = new HashMap<>(roles.size());
        if (DomainEnum.PLATFORM.name().equals(Me.domainCode())) {
            return;
        } else if (DomainEnum.TENANT.name().equals(Me.domainCode()) && Me.categoryCodes().contains(UserCategoryEnum.TENANT_ADMINISTRATOR.name())){
            List<String> deptFullPaths = Tools.Coll.distinct(this.deptApi.loadFullPathAll());
            dataScopeMap.put(GLOBAL_MENU_ID, deptFullPaths);
        } else {
            //如果角色列表中有一个角色的数据范围是“全部数据”,则给每个菜单都赋予所有单位权限
            if (roles.stream().anyMatch(role -> role.getDataScopeType().equals(DataScopeTypeEnum.ALL))) {
                List<String> deptFullPaths = Tools.Coll.distinct(this.deptApi.loadFullPathAll());
                Tools.Coll.distinct(Tools.Coll.flatMap(menuIdMap.values(), Collection::stream)).forEach(menuId -> dataScopeMap.put(menuId, deptFullPaths));
            } else if (Tools.Coll.isNotBlank(departments)) {
                // 遍历所有的角色
                roles.forEach(role -> {
                    // 根据角色信息和用户所属单位获取该角色的单位权限
                    List<String> currentRoleDataScopeCodes = this.parseDataScopeCode(role.getDataScopeType(), role.getDataScopeCodes(), departments);
                    if (Tools.Coll.isNotBlank(currentRoleDataScopeCodes) && menuIdMap.containsKey(role.getRoleId())) {
                        // 轮询该角色对应的菜单
                        menuIdMap.get(role.getRoleId()).forEach(menuId -> dataScopeMap.computeIfAbsent(menuId, k -> new ArrayList<>()).addAll(currentRoleDataScopeCodes));
                    }
                });
            } else {
                return;
            }
        }
        if (Tools.Coll.isNotBlankMap(dataScopeMap)) {
            //缓存菜单信息到redis
            Map<String, String> cacheDataScopeMap = new HashMap<>(dataScopeMap.size());
            dataScopeMap.forEach((menuId, dataScopeCodes) -> cacheDataScopeMap.put(menuId, Tools.Coll.join(dataScopeCodes)));
            this.redisTemplate.opsForHash().putAll(CacheConstants.userAuthDetailsFormat(username, Me.tenantCode()), cacheDataScopeMap);
        }
    }


    /**
     * 解析数据权限码
     *
     * @param dataScopeType      数据权限类型
     * @param roleDataScopeCodes 角色数据权限码列表
     * @param departments        用户所属部门列表
     * @return 数据范围代码列表
     */
    private List<String> parseDataScopeCode(DataScopeTypeEnum dataScopeType, List<String> roleDataScopeCodes, List<AuthorizationUserDepartment> departments) {
        List<String> dataScopeCodes = new ArrayList<>();
        //指定部门的数据,且部门编号不为空,根据部门编号获取所有的部门全路径
        if (DataScopeTypeEnum.DEFINE.equals(dataScopeType) && Tools.Coll.isNotBlank(roleDataScopeCodes)) {
            List<String> deptFullPaths = this.deptApi.loadFullPathByDeptIds(roleDataScopeCodes);
            if (Tools.Coll.isNotBlank(deptFullPaths)) {
                dataScopeCodes.addAll(deptFullPaths);
            }
        }
        //本部门及以下数据,且用户所属部门列表不为空,获取用户所属部门及所有的子部门的全路径
        if (DataScopeTypeEnum.DEPT_WITH_CHILD.equals(dataScopeType) && Tools.Coll.isNotBlank(departments)) {
            List<String> deptFullPaths = this.deptApi.loadSubFullPathByDeptIds(Tools.Coll.convertList(departments, AuthorizationUserDepartment::getDeptId));
            if (Tools.Coll.isNotBlank(deptFullPaths)) {
                dataScopeCodes.addAll(deptFullPaths);
            }
        }
        //本部门的数据,且用户所属部门列表不为空,直接拿所属部门的全路径
        if (DataScopeTypeEnum.DEPT.equals(dataScopeType) && Tools.Coll.isNotBlank(departments)) {
            List<String> deptFullPaths = this.deptApi.loadFullPathByDeptIds(Tools.Coll.convertList(departments, AuthorizationUserDepartment::getDeptId));
            if (Tools.Coll.isNotBlank(deptFullPaths)) {
                dataScopeCodes.addAll(deptFullPaths);
            }
        }
        return dataScopeCodes;
    }

}
